#include "DiskAbstraction.h"

#include <stdlib.h>
#include <sstream>
#include <fstream>

using std::ifstream;
using std::ofstream;
using std::fstream;
using std::ios;
using std::stringstream;


DiskAbstraction::DiskAbstraction(string& fileName, unsigned int blockSize, unsigned int diskSize):
_fileName(fileName),
_blockSize(blockSize),
_totalBlockCount(0),
_diskSize(0),
_isNew(true)
{	
	ifstream iStream(_fileName.c_str(),ios::binary|ios::in);
	if(!iStream.is_open()) {//disk does not exists		
		ofstream oStream(_fileName.c_str(),ios::binary|ios::out);
		if(!oStream.is_open()) {
			stringstream ss;
			ss<<"Cannot create new "<<_fileName<<"..."<<endl;
			throw Exception(ss.str());
		} else {					
			if (blockSize<64)
			{
				stringstream ss;
				ss<<"Illegal configuration file. Minimum block size is 64. Given: "<<_blockSize<<endl;
				throw Exception(ss.str());
			}
			_totalBlockCount = diskSize / blockSize;
			_diskSize = _totalBlockCount * blockSize;
			if (_totalBlockCount<5)
			{
				stringstream ss;
				ss<<"Illegal configuration file. Minimum total blocks size is 5. Given: "<<_totalBlockCount<<endl;
				throw Exception(ss.str());
			}
			cout<<"File system does not exists. Creating new "<<_fileName<<" ["<<_blockSize<<", "<<_diskSize<<"]..."<<endl;
			oStream.write((char*)&_diskSize, sizeof(unsigned int));
			oStream.write((char*)&_blockSize, sizeof(unsigned int));			
			oStream.close();
		}
	} else {
		iStream.read((char*)&_diskSize, sizeof(unsigned int));
		iStream.read((char*)&_blockSize, sizeof(unsigned int));
		
		iStream.close();
		_totalBlockCount = _diskSize / _blockSize;
		_isNew=false;
	}
}

DiskAbstraction::~DiskAbstraction(void)
{
}

void DiskAbstraction::read(BlockId id, size_t baseSize, void* buffer){
	if (id>=_totalBlockCount)
	{
		stringstream ss;
		ss<<"Cannot read from block id: "<<id<<". Index out of bounds."<<endl;
		throw Exception(ss.str());
	}
	ifstream iStream(_fileName.c_str(),ios::in | ios::binary);
	if (iStream.is_open()) {
		iStream.seekg(id*_blockSize);
		iStream.read((char*)buffer, _blockSize);
		iStream.close();
	} else {
		throw Exception("Error opening file system file!");
	}
}

void DiskAbstraction::write(BlockId id, void* data){
	if (id>=_totalBlockCount)
	{
		stringstream ss;
		ss<<"Cannot write to block id: "<<id<<". Index out of bounds."<<endl;
		throw Exception(ss.str());
	}
	fstream fStream(_fileName.c_str(), ios::in|ios::out|ios::binary);
	if (fStream.is_open()) {
		fStream.seekp(id*_blockSize, ios::beg);
		fStream.write((char*)data, _blockSize);
		fStream.close();
	} else {
		throw Exception("Error opening file system file!");
	}
}

unsigned int DiskAbstraction::getTotalBlockCount(){
	return _totalBlockCount;
}

unsigned int DiskAbstraction::getDiskSize(){
	return _diskSize;
}
unsigned int DiskAbstraction::getBlockSize(){
	return _blockSize;
}


void DiskAbstraction::seekAndRead(BlockId id, size_t seekSize, unsigned int offset, void* container, size_t containerSize){
	if (id>=_totalBlockCount)
	{
		stringstream ss;
		ss<<"Cannot read from block id: "<<id<<". Index out of bounds."<<endl;
		throw Exception(ss.str());
	}
	ifstream iStream(_fileName.c_str(),ios::in | ios::binary);
	if (iStream.is_open()) {
		iStream.seekg(id*_blockSize+(offset*seekSize));
		iStream.read((char*)container, containerSize);
		iStream.close();
	} else {
		throw Exception("Error opening file system file!");
	}
}

void DiskAbstraction::seekAndWrite(BlockId id, size_t seekSize, unsigned int offset, void* value, size_t containerSize){
	if (id>=_totalBlockCount)
	{
		stringstream ss;
		ss<<"Cannot write to block id: "<<id<<". Index out of bounds."<<endl;
		throw Exception(ss.str());
	}
	fstream fStream(_fileName.c_str(), ios::in|ios::out|ios::binary);
	if (fStream.is_open()) {
		fStream.seekp(id*_blockSize+(offset*seekSize), ios::beg);
		fStream.write((char*)value, containerSize);
		fStream.close();
	} else {
		throw Exception("Error opening file system file!");
	}
}

bool DiskAbstraction::isNew(){
	return _isNew;
}
